﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Reflection;
using System.Reflection.Emit;
using NMapper.Utils;

namespace NMapper.Mappings
{
    public class TransientMapping<TDatabase, TDatabaseEntity, TDbValue> : IDbValueMapping<TDatabase, TDatabaseEntity, TDbValue> 
        where TDatabase : IDisposable
    {
        private Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>> dbSelector;
        private Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue> dbSelectorFunc;
        private PropertyInfo property;

        public TransientMapping(Expression<Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue>> dbSelector)
        {
            this.dbSelector = dbSelector;
            dbSelectorFunc = dbSelector.Compile();
        }

        LambdaExpression IDbSelector<TDatabase>.DbSelectorExpression
        {
            get { return dbSelector; }
        }

        Func<MapContext<TDatabase>, object, object> IDbSelector<TDatabase>.DbSelector
        {
            get { return (mapContext, x) => DbSelector(mapContext, (TDatabaseEntity)x); }
        }

        public Func<MapContext<TDatabase>, TDatabaseEntity, TDbValue> DbSelector
        {
            get { return dbSelectorFunc; }
        }

        public PropertyInfo TransientProperty
        {
            get { return property; }
        }

        public void DefineTransientType(ModuleBuilder module, TypeBuilder type)
        {
            var dbPropertyPath = dbSelector.GetPropertyPath();

            var propertyName = string.Join("_", dbPropertyPath.Select(x => x.Name));
            var propertyType = dbPropertyPath.Last().PropertyType;

            property = type.DefineProperty(propertyName, propertyType);
        }

        public void DefineTransientInitialization(MapContext<TDatabase> mapContext, Type transientType, List<MemberBinding> initializers, ParameterExpression parameterExpression)
        {
            property = transientType.GetProperty(property.Name);

            var lambdaBinder = new LambdaBinder();
            var simpleDbSelector = dbSelector.PartialApplication(Expression.Constant(mapContext), null).Simplify();
            var selector = lambdaBinder.Bind(simpleDbSelector, parameterExpression);

            var lhs = property;
            var rhs = selector.Body;
            
            initializers.Add(Expression.Bind(lhs.GetSetMethod(), rhs));
        }

        public virtual void MapToOutput(MapContext<TDatabase> mapContext, object input, object output)
        {
        }

        public virtual void MapToInput(MapContext<TDatabase> mapContext, object output, object input)
        {
        }

        public virtual void OnTransientInitialized(MapContext<TDatabase> mapContext, Array transient)
        {
        }

        public override string ToString()
        {
            return string.Format("PropertyMapping({0})", dbSelector);
        }
    }
}